package org.apache.ocean.name;

import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;
import org.apache.ocean.CSequence;
import org.apache.ocean.ClusterConstants;
import org.apache.ocean.ClusterRegistry;
import org.apache.ocean.ClusterUtil;
import org.apache.ocean.OceanXML;
import org.apache.ocean.RestartableTimer;
import org.apache.ocean.Service;
import org.apache.ocean.TimeoutState;
import org.apache.ocean.TransactionID;

/**
 * 
 * @author jasonr
 */
// TODO: node needs to save assigned cellid
public class NameService implements Service {
	public static Logger log = Logger.getLogger(NameService.class.getName());
	public Date startDate;
	public SQLNameDatabase nameDatabase;
	public NameConfig nameConfig;
	public NameClient nameClient;
	private String id;
	public Set<URL> nameURLs;
	private CSequence replicationIDSequence;
	private String serviceStatus;
	private RestartableTimer replicationPollTimer;
	private RestartableTimer nodeCleanerTimer;

	public NameService() {
	}
  
	public String getID() {
		return id;
	}
	
	public class NodeCleaner implements RestartableTimer.TimeoutRunnable {
    public void run(TimeoutState timeoutState) {
    	try {
    		List<Node> nodes = nameDatabase.findNodes();
    		for (Node node : nodes) {
    			if (node.reportDate == null) {
    				cleanNodeReportFields(node);
    			} else {
    				long duration = System.currentTimeMillis() - node.reportDate.getTime();
    				if (duration > 10*1000) {
    					cleanNodeReportFields(node);
    				}
    			}
    		}
    	} catch (Throwable throwable) {
    		log.log(Level.SEVERE, "", throwable);
    	}
    }
    
    private void cleanNodeReportFields(Node node) throws Exception {
    	node.reportedCellID = ClusterConstants.NULL_STRING;
    	node.reportedMasterID = ClusterConstants.NULL_STRING;
    	node.reportedMasterIDSinceDate = ClusterConstants.NULL_DATE;
    	node.reportedStatusSinceDate = ClusterConstants.NULL_DATE;
    	node.reportedStatus = ClusterConstants.NULL_STRING;
    	node.highestCellTransactionIDDatabase = ClusterConstants.NULL_TRANSACTIONID;
    	node.highestCellTransactionIDIndex = ClusterConstants.NULL_TRANSACTIONID;
    	nameDatabase.updateNodeSet(node, false);
    }
	}
	
	public class ReplicationPoll implements RestartableTimer.TimeoutRunnable {
    public void run(TimeoutState timeoutState) {
    	try {
    		NameInfo masterNameInfo = nameClient.getMasterNameInfoNoWait();
    		if (masterNameInfo != null) {
    			OceanXML response = nameClient.getNodes(masterNameInfo.url, nameClient.getDefaultHttpTimeout(timeoutState));
    			List<Node> nodes = response.bodyToList(new Node.Transform());
    			for (Node node : nodes) {
    				// TODO: perform batch update doing connection.commit at end
    				nameDatabase.updateNode(node);
    			}
    		}
    	} catch (Throwable throwable) {
    		log.log(Level.SEVERE, "", throwable);
    	}
    }
	}
	
	public void resetSequences() throws Exception {
		TransactionID id = nameDatabase.findHighestReplicationID();
		TransactionID clone = (TransactionID)id.clone();
		clone.increment();
		replicationIDSequence.set(clone.value);
	}

	public TransactionID getNextReplicationID() throws Exception {
		return new TransactionID(replicationIDSequence.next());
	}

	public String getServiceStatus() {
		return serviceStatus;
	}

	public OceanXML.ServerInfo getServerInfo() {
		return new OceanXML.ServerInfo(id, "name", getServiceStatus());
	}

	public NameInfo createNameInfo() throws Exception {
		NameInfo nameInfo = new NameInfo();
		nameInfo.id = id;
		nameInfo.startDate = startDate;
		nameInfo.url = nameConfig.url;
		nameInfo.highestReplicationIDDatabase = nameDatabase.findHighestReplicationID();
		NameInfo masterNameInfo = nameClient.getMasterNameInfoNoWait();
		if (masterNameInfo != null) {
			nameInfo.masterNameID = masterNameInfo.id;
		}
		return nameInfo;
	}

	public ReportedNameInfos getReportedNameInfos() throws Exception {
		if (nameClient.myReportedNameInfos == null) {
			List<NameInfo> nameInfos = new ArrayList<NameInfo>();
			NameInfo nameInfo = createNameInfo();
			nameInfo.masterNameID = nameInfo.id;
			nameInfos.add(nameInfo);
			ReportedNameInfos myReportedNameInfos = new ReportedNameInfos(id, nameInfos, new Date());
			return myReportedNameInfos;
		} else {
			return nameClient.myReportedNameInfos;
		}
	}

	public class NameEventHandler extends NameClient.EventListener {
		public void masterNameChanged(NamesInfosGroup namesInfosGroup) {
			try {
				NameInfo masterNameInfo = nameClient.getMasterNameInfoNoWait();
				if (masterNameInfo != null) {
					if (StringUtils.equals(getID(), masterNameInfo.id)) {
						resetSequences();
					}
				} else {
					log.info("masterNameChanged and masterNameInfo null");
				}
			} catch (Throwable throwable) {
        log.log(Level.SEVERE, "", throwable);
			}
		}
	}

	public static NameService getNameService() throws Exception {
		NameService nameService = (NameService) ClusterRegistry.get("nameService");
		if (nameService == null) {
			nameService = new NameService();
		}
		return nameService;
	}

	public void start() throws Exception {
		startDate = new Date();
		nameConfig = NameConfig.loadNameConfig();
		id = ClusterUtil.loadNodeID(nameConfig.idFile);
		if (id == null)
			throw new Exception("id null");
		replicationIDSequence = new CSequence("replicationid", ClusterUtil.getNameHome());
		nameURLs = ClusterUtil.loadURLs(nameConfig.nameURLsFile);
		nameDatabase = new SQLNameDatabase(nameConfig.databaseDirectory);
		nameClient = new NameClient(id, nameURLs, new NameEventHandler());
		replicationPollTimer = new RestartableTimer("replicationPollTimer", new ReplicationPoll(), 5 * 1000, 30 * 1000);
		nodeCleanerTimer = new RestartableTimer("nodeCleanerTimer", new NodeCleaner(), 1000*2, 5*1000);
	}

	public void stop() throws Exception {
		try {
			if (nameDatabase != null) {
				nameDatabase.close();
			}
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
		}
		try {
			if (nameClient != null) {
				nameClient.shutdown();
			}
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
		}
	}
}
